package com.itheima.springbootsecurity.config;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.itheima.springbootsecurity.domain.Resource;
import com.itheima.springbootsecurity.filter.JwtAuthenticationTokenFilter;
import com.itheima.springbootsecurity.handler.AnonymousAuthenticationHandler;
import com.itheima.springbootsecurity.handler.CustomAccessDeniecHandler;
import com.itheima.springbootsecurity.handler.LoginFailureHandler;
import com.itheima.springbootsecurity.mapper.ResourceMapper;
import com.itheima.springbootsecurity.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * SpringSecurity配置类
 *
 * @author wuhaohua
 */
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    /**
     * 自定义的用于校验token的过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    /**
     * 自定义的已登录用户但访问无权限资源的处理器
     */
    @Autowired
    private CustomAccessDeniecHandler customAccessDeniecHandler;
    /**
     * 客户端进行登录时出现异常，或者匿名用户访问受限资源的处理器
     */
    @Autowired
    private AnonymousAuthenticationHandler anonymousAuthenticationHandler;
    /**
     * 用户登录校验失败处理器
     */
    @Autowired
    private LoginFailureHandler loginFailureHandler;
    @Autowired
    private ResourceService resourceService;
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 注入密码编码器
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入登录时需要调用的AuthenticationManager.authenticate方法进行账号密码校验
     *
     * @param configuration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    /**
     * 配置安全过滤器
     *
     * @param http
     * @return 安全过滤器链对象
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 配置关闭csrf机制
        http.csrf(csrf -> csrf.disable());
        // STATELESS（无状态）：表示应用程序是无状态的，不会创建会话
        http.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        // 用户认证校验失败处理器
        http.formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.failureHandler(loginFailureHandler));

        // 配置请求拦截方式
        // requestMatchers方法中的路径默认有所有权限
        http.authorizeHttpRequests(requests -> {
            // 获取不需要授权的资源路径
            List<String> noAuthResourcePathList = resourceService.getNoAuthResourcePathList();
            for (String path : noAuthResourcePathList) {
                if (StringUtils.hasLength(path)) {
                    // 记录一些不需要授权的资源路径
                    requests = requests.requestMatchers(path).permitAll();
                }
            }
            requests
                    // 其他任何路径
                    .anyRequest()
                    // 需要通过校验才能访问
                    .authenticated();
        });

        // 把token校验过滤器添加到过滤链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        // 将自定义的异常处理器注入
        http.exceptionHandling(exceptionHandling -> {
            exceptionHandling.accessDeniedHandler(customAccessDeniecHandler);
            exceptionHandling.authenticationEntryPoint(anonymousAuthenticationHandler);
        });

        // 允许跨域请求
        return http.build();
    }
}
